﻿using Microsoft.Extensions.DependencyInjection;
using mozhi.smarterp.Finance.Accounts;
using mozhi.smarterp.Finance.Accounts.Repository;
using mozhi.smarterp.Finance.Enums;
using mozhi.smarterp.Finance.Finances.Repository;
using mozhi.smarterp.Finance.Querys;
using Mozhi.Abp.Domain.BasicData.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Guids;

namespace mozhi.smarterp.Finance.Finances.Services.EndPeriodProcess
{
    public class EndPeriodProcessorBase<TData> : IEndPeriodProcessor where TData : class
    {
        protected List<TData> _data;

        protected EndPeriodProcessItem _item;

        protected int _period;

        protected List<Account> _accounts;

        protected IServiceProvider _serviceProvider;

        protected IGuidGenerator _guidGenerator;

        protected EndPeriodProcessResult _processResult;

        protected JournalManager _journalManager;

        protected IOrganizationRepository _organizationRepository;


        public EndPeriodProcessorBase(EndPeriodProcessItem item, int period, IServiceProvider serviceProvider)
        {
            _item = item;
            _period = period;
            _serviceProvider = serviceProvider;
            _guidGenerator = _serviceProvider.GetService(typeof(IGuidGenerator)) as IGuidGenerator;
            _journalManager = _serviceProvider.GetService(typeof(JournalManager)) as JournalManager;
            _organizationRepository = _serviceProvider.GetService<IOrganizationRepository>();
        }


        /// <summary>
        /// 获取处理结果
        /// </summary>
        /// <returns></returns>
        public virtual async Task<EndPeriodProcessResult> GetProcessResultAsync()
        {
            await ValidateItemAccountAsync();

            await LoadCalculationDataAsync();

            var result = await HandleDataAsync();

            return result;
        }

       

        /// <summary>
        /// 生成凭证
        /// </summary>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task<Journal> GenerateJournal()
        {
            await ValidateBeforeGenerateJournal();

            await LoadCalculationDataAsync();

            Journal journal = await HandleGenerateJournalAsync();


            //合并分录
            var groups = journal.Items.GroupBy(x => new { x.AccountId, x.DimensionGroupValueId, x.JournalDirection, x.CurrencyId });

            var journalItems = new List<JournalItem>();

            var index = 1;

            foreach (IGrouping<object, JournalItem> info in groups)
            {
                var journalItem = info.First();

                var totalDebitAmountFr = info.Where(x => x.DebitAmount.HasValue).Sum(x => x.DebitAmount);

                var totalCreditAmountFr = info.Where(x => x.CreditAmount.HasValue).Sum(x => x.CreditAmount);

                journalItem.ChangeAmount(totalDebitAmountFr, totalCreditAmountFr, index);

                index++;

                journalItems.Add(journalItem);
            }

            journal.ChangeItems(journalItems);


            return journal;
        }

        #region
        

        protected virtual async Task<EndPeriodProcessResult> HandleDataAsync()
        {
            return default(EndPeriodProcessResult);
        }

        protected virtual async Task<Journal> HandleGenerateJournalAsync()
        {
            return default(Journal);
        }

        protected virtual async Task LoadCalculationDataAsync()
        {
            var accountBalanceRepository = _serviceProvider.GetService(typeof(IAccountBalanceRepository)) as IAccountBalanceRepository;

            _data = await accountBalanceRepository.FindAsync(new AccountBalanceQuery() { Period = _period, IncludeCurrency = false, IncludeDimension = false }) as List<TData>;
        }

        protected virtual async Task ValidateItemAccountAsync()
        {
            if (_item == null)
                throw new ArgumentNullException();

            await LoadAccountAsync();

            var settings = _item.Settings;

            foreach (var setting in settings)
            {
                var matchAccount = _accounts.FirstOrDefault(x => x.Code == setting.CustomAccountCode);

                if (matchAccount == null)
                    throw new UserFriendlyException($"期末处理项：{_item.Name}的{setting.Name}设置的科目不存在或者未设置", "100000");

                if(!matchAccount.IsLeaf)
                    throw new UserFriendlyException($"期末处理项：{_item.Name}的{setting.Name}设置的科目不是子级科目", "100000");
            }
        }

        protected Account GetAccount(string customAccountCode)
        {
            return _accounts.FirstOrDefault(x => x.Code == customAccountCode);
        }

        protected virtual async Task LoadAccountAsync()
        {
            var accountRepository = _serviceProvider.GetService(typeof(IAccountRepository)) as IAccountRepository;

            var accountCodes = _item.Settings.Where(x => x.CustomAccountCode.IsNullOrEmpty()).Select(x => x.CustomAccountCode).Distinct().ToList();

            _accounts = await accountRepository.GetListAsync();

            _accounts = _accounts.Where(x => accountCodes.Contains(x.Code)).ToList();
        }

        /// <summary>
        /// 获取期末处理科目设置
        /// </summary>
        /// <param name="defaultCode"></param>
        /// <returns></returns>
        protected virtual EndPeriodProcessItemSetting GetEndPeriodProcessSettingAsync(string defaultCode)
        {
            if (defaultCode.IsNullOrEmpty() || _item == null || _item.Settings == null)
            {
                return null;
            }

            return _item.Settings.FirstOrDefault(x => x.DefaultAccountCode == defaultCode);
        }

        /// <summary>
        /// 获取结转损益凭证余额
        /// </summary>
        /// <returns></returns>
        protected async Task<List<AccountBalance>> GetProfitLossJournalBalanceAsync()
        {
            var result = new List<AccountBalance>();

            var endPeriodProcessRepository = _serviceProvider.GetServices(typeof(IEndPeriodProcessResultRepository)) as IEndPeriodProcessResultRepository;

            var profitLossResults = await endPeriodProcessRepository.FindAsync(new EndPeriodProcessResultQuery() { Type = Enums.EndPeriodProcessType.TransferProfitLoss, Period = _period });

            if (profitLossResults.Count == 0)
                return result;

            var profitLossResult = profitLossResults.FirstOrDefault();

            if (profitLossResult == null || !profitLossResult.JournalId.HasValue)
                return result;

            var journalReposiotry = _serviceProvider.GetServices(typeof(IJournalRepository)) as IJournalRepository;

            var journals = await journalReposiotry.FindAsync(new JournalPageQuery() { PageIndex = 1, PageSize = int.MaxValue, Id = profitLossResult.JournalId });

            if (journals.Count == 0 || journals.First().Status != DocumentStatusType.Approved)
                return result;

            result = _journalManager.ConvertToBalance(journals.First());

            return result;
        }


        protected virtual async Task ValidateBeforeGenerateJournal()
        {
            //校验科目是否都已经设置
            await ValidateItemAccountAsync();

            var processResult = await GetEndPeriodResultAsync();

            if (processResult != null && processResult.JournalId.HasValue)
            {
                //获取凭证信息
                var journal = await GetJournalAsync(processResult.JournalId);

                if (journal != null)
                    throw new UserFriendlyException($"期末处理的凭证已生成：{journal.Number} ,请先删除", "100000");
            }

            if (processResult.Items == null || processResult.Items.Count == 0)
                throw new UserFriendlyException($"期末处理还未测算，请先测算", "100000");

            if (!processResult.NeedProcess)
                throw new UserFriendlyException($"期末处理的测算结果未不要处理，不能生成凭证", "100000");

            _processResult = processResult;
        }

        /// <summary>
        /// 获取期末处理结果
        /// </summary>
        /// <returns></returns>
        protected async Task<EndPeriodProcessResult> GetEndPeriodResultAsync()
        {
            var reposiotry = _serviceProvider.GetService(typeof(IEndPeriodProcessResultRepository)) as IEndPeriodProcessResultRepository;

            var results = await reposiotry.FindAsync(new EndPeriodProcessResultQuery() { IncludeDetail = true, ItemId = _item.Id, Period = _period });

            return results.FirstOrDefault();
        }

        protected async Task<EndPeriodProcessResult> GetEndPeriodResultAsync(int period)
        {
            var reposiotry = _serviceProvider.GetService(typeof(IEndPeriodProcessResultRepository)) as IEndPeriodProcessResultRepository;

            var results = await reposiotry.FindAsync(new EndPeriodProcessResultQuery() { IncludeDetail = true, ItemId = _item.Id, Period = period });

            return results.FirstOrDefault();
        }

        protected async Task<Journal> GetJournalAsync(Guid? id)
        {
            var reposiotry = _serviceProvider.GetService(typeof(IJournalRepository)) as IJournalRepository;

            var journals = await reposiotry.FindAsync(new JournalPageQuery() { IncludeDetail = false, PageIndex = 1, PageSize = int.MaxValue, Id = id });

            return journals.FirstOrDefault();
        }

        /// <summary>
        /// 获取凭证分录
        /// </summary>
        /// <param name="amount"></param>
        /// <param name="account"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        protected async Task<JournalItem> GetJournalItemAsync(string summary , decimal amount, Account account, Guid? dimensionGroupValueId , int index, AccountDirectionType direction)
        {
            var baseCurrencyId = (await _organizationRepository.GetAsync()).StandardCurrencyId;

            var journalItem = new JournalItem(_guidGenerator.Create(), summary, account.Id, direction, dimensionGroupValueId, baseCurrencyId.Value, 1, amount,
                direction == AccountDirectionType.Debit ? amount : null, direction == AccountDirectionType.Credit ? amount : null, index);

            return journalItem;
        }

        protected async Task<JournalNumberPrefix> GetJournalDefaultNumberPrefix()
        {
            var defaultWords = await (_serviceProvider.GetService(typeof(IJournalNumberPrefixRepository)) as IJournalNumberPrefixRepository)
                .FindAsync(new PageQuery() { PageIndex = 1, PageSize = 1 });
            return defaultWords.FirstOrDefault();
        }


        /// <summary>
        /// 获取第一个叶子节点科目
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        protected virtual Account GetFirstLeafAccount(string code)
        {
            var account = GetAccount(code);

            if (account == null)
                return account;

            return GetLeafAccount(account);
        }


        /// <summary>
        /// 获取科目的叶子节点
        /// </summary>
        /// <returns></returns>
        protected virtual List<Account> GetFirstLeafAccounts()
        {
            var customeCodes = _item.Settings.Select(x => x.CustomAccountCode);

            var result = new List<Account>();

            foreach (var code in customeCodes)
            {
                var account = GetFirstLeafAccount(code);
                result.Add(account);
            }

            return result;

        }


        /// <summary>
        /// 获取叶子节点
        /// </summary>
        /// <param name="parentAccount"></param>
        /// <returns></returns>
        protected Account GetLeafAccount(Account parentAccount)
        {
            if (parentAccount == null)
            {
                return null;
            }

            var children = _accounts.Where(x => x.ParentId == parentAccount.Id).ToList();

            //如果自己就是叶子节点
            if (children.Count == 0)
            {
                return parentAccount;
            }

            var firstChild = children.OrderBy(x => x.Code).First();

            var leafAccount = GetLeafAccount(firstChild);


            return leafAccount;
        }

        #endregion
    }
}
